package com.avo.ramcache.model;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LruSoftHashMap<K, V> {

    private final Map<K, Reference<V>> cacheMap = new ConcurrentHashMap<>();
    private final Map<K, V> hardCache;
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    private int clearNum;

    public LruSoftHashMap(Map<K, V> cacheMap) {
        this.hardCache = cacheMap;
    }

    public int getCacheMapSize() {
        return cacheMap.size();
    }

    public V get(K key) {
        readLock.lock();
        try {
            V result = hardCache.get(key);
            if (result != null) {
                return result;
            }
            Reference<V> ref = cacheMap.get(key);
            if (ref != null) {
                result = ref.get();
                if (result == null) {
                    cacheMap.remove(key);
                } else {
                    hardCache.put(key, result);
                }
            }
            return result;
        } finally {
            readLock.unlock();
        }
    }

    public void put(K key, V value) {
        writeLock.lock();
        try {
            Entry entry = new Entry(key, value, null);
            cacheMap.put(key, entry);
            hardCache.put(key, value);
            if (++clearNum > 1000) {
                clearNum = 0;
                clearCacheMap();
            }
        } finally {
            writeLock.unlock();
        }
    }

    public V remove(K key) {
        writeLock.lock();
        try {
            V result = hardCache.remove(key);
            Reference<V> ref = cacheMap.remove(key);
            if (result != null) {
                if (ref != null) {
                    result = ref.get();
                }
            }
            return result;
        } finally {
            writeLock.unlock();
        }
    }

    private void clearCacheMap() {
        Iterator<Map.Entry<K, Reference<V>>> iterator = cacheMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<K, Reference<V>> next = iterator.next();
            Reference<V> value = next.getValue();
            if (value != null && value.get() == null) {
                iterator.remove();
            }
        }
    }

    private class Entry extends WeakReference<V> {
        private K key;

        public Entry(K key, V referent, ReferenceQueue<? super V> q) {
            super(referent, q);
        }

        K getKey() {
            return key;
        }
    }

    public Collection<V> values() {
        return hardCache.values();
    }

}
